package com.skyable.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.skyable.common.common.ResponseResult;
import com.skyable.common.constants.exception.AuthExcpConstants;
import com.skyable.common.constants.param.CommonParams;
import com.skyable.common.constants.exception.CommonExcpConstants;
import com.skyable.common.constants.redis.RedisKeyConstants;
import com.skyable.common.constants.redis.RedisTimeoutConstants;
import com.skyable.common.utils.JwtTokenUtil;
import com.skyable.common.utils.StringUtil;
import com.skyable.gateway.entity.OAuth2User;
import com.skyable.gateway.utils.RedisUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author GaoYuan
 * @title: TokenGlobalFilter
 * @projectName spring-cloud-demo
 * @description: gateway全局过滤器
 * @date 2021/3/718:31
 */
@Slf4j
@Component
public class OAuthGlobalFilter extends BaseGlobalFilter implements GlobalFilter, Ordered {

    @Value("${server.auth.url}")
    private String serverAuthUrl;

    private RestTemplate restTemplate = new RestTemplate();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //判断是否需要过滤
        String path = request.getURI().getPath();
        if(isRelease(path)) {
            return chain.filter(exchange);
        }

        ResponseResult tokenFilterResult = tokenFilter(request);
        if(tokenFilterResult.isSuccess()) {
            Object result = tokenFilterResult.getResult();
            return success(exchange,chain, (Map<String, Object>) result);
        }

        ResponseResult oauthFilterResult = oauthFilter(request);
        if(oauthFilterResult.isSuccess()) {
            Object result = oauthFilterResult.getResult();
            return success(exchange,chain, (Map<String, Object>) result);
        }

        return error(response, oauthFilterResult.getCode(),oauthFilterResult.getMessage());
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 用于本平台生成的jwt token的验证
     * @param request
     * @return
     */
    private ResponseResult tokenFilter(ServerHttpRequest request) {
        Map<String,Object> result = new HashMap<>(2);
        //获取请求路径
        String path = request.getURI().getPath();
        //从请求头中取得token
        String token = request.getHeaders().getFirst(CommonParams.HEADER_AUTHORIZATION);
        if (StringUtils.isEmpty(token)) {
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED,CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
        }

        //验证token是否正确
        String userName = "";
        try {
            userName = JwtTokenUtil.getUsernameFromToken(token);
            Claims claims = JwtTokenUtil.getAllClaimsFromToken(token);
            Long domainId = claims.get(CommonParams.HEADER_USER_DOMAIN_ID, Long.class);
            if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(domainId)) {
                return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED, CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
            }
            result.put(CommonParams.HEADER_USER_DOMAIN_ID, domainId);
            result.put(CommonParams.HEADER_USER_NAME, userName);
        } catch (Exception e) {
            log.error("token parse error, and entry OauthGlobalFilter to verification");
            //这里是进入下一个过滤器，而不是直接返回错误
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE, e.getMessage());
        }

        //验证用户信息是否过期,这里验证的是redis中的token
        String redisUserKey = RedisKeyConstants.WEB_USER_INFO_KEY + userName;
        Boolean isValidate = RedisUtil.hasKey(redisUserKey);
        if (!isValidate) {
            return ResponseResult.error(AuthExcpConstants.ERROR_CODE_TOKEN_EXPIRED,AuthExcpConstants.ERROR_MSG_TOKEN_EXPIRED);
        }

        //更新用户信息有效期
        RedisUtil.expire(redisUserKey,RedisTimeoutConstants.USER_INFO_TIME_OUT, TimeUnit.SECONDS);

        //判断用户是否有接口权限
        List<String> permissionCodeList = null;
        String userStr = RedisUtil.get(redisUserKey);
        JSONObject userObj = JSONObject.parseObject(userStr);
        if(userObj != null && userObj.containsKey(CommonParams.USER_PERMISSION_CODE_LIST)) {
            permissionCodeList = userObj.getObject(CommonParams.USER_PERMISSION_CODE_LIST, List.class);
        }

        if(!isAuthorizated(path,permissionCodeList)) {
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE_FORBIDDEN, CommonExcpConstants.ERROR_MSG_FORBIDDEN);
        }

        return ResponseResult.success(result);
    }

    /**
     * oauth token验证
     * @param request
     * @return
     */
    private ResponseResult oauthFilter(ServerHttpRequest request) {
        Map<String,Object> result = new HashMap<>(2);
        //获取请求路径
        String path = request.getURI().getPath();
        //从请求中取得token信息
        String token = request.getQueryParams().getFirst(CommonParams.HEADER_AUTHORIZATION_TOKEN);
        if(StringUtil.isEmpty(token)) {
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED,CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
        }
        //从请求头中获取basic认证信息
        String authorizationHeader = request.getHeaders().getFirst(CommonParams.HEADER_AUTHORIZATION);
        if(StringUtil.isEmpty(authorizationHeader)) {
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED,CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
        }

        //调用认证服务接口进行认证，需要authorization头信息和token信息
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(serverAuthUrl).queryParam(CommonParams.TOKEN, token);
        URI url = builder.build().encode().toUri();
        HttpEntity<?> entity = new HttpEntity<>(request.getHeaders());

        try {
            ResponseEntity<OAuth2User> authResponse = restTemplate.exchange(url, HttpMethod.POST, entity, OAuth2User.class);
            log.info("oauth request: {}, response body: {}, response status: {}", entity, authResponse.getBody(), authResponse.getStatusCode());
            OAuth2User auth2User = authResponse.getBody();
            if(auth2User == null) {
                return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED,CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
            }

            if(!auth2User.getActive()) {
                return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED,CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
            }

            List<String> authorities = auth2User.getAuthorities();
            if(!isAuthorizated(path, authorities)) {
                return ResponseResult.error(CommonExcpConstants.ERROR_CODE_FORBIDDEN, CommonExcpConstants.ERROR_MSG_FORBIDDEN);
            }

            String userName = auth2User.getUser_name();
            result.put(CommonParams.HEADER_USER_NAME, userName);
            return ResponseResult.success(result);
        } catch (RestClientException e) {
            log.error("user oauth failed.", e);
            return ResponseResult.error(CommonExcpConstants.ERROR_CODE_UNAUTHORIZED, CommonExcpConstants.ERROR_MSG_UNAUTHORIZED);
        }
    }
}